package com.gitee.qdbp.jdbc.sql;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import com.gitee.qdbp.able.jdbc.model.DbFieldName;
import com.gitee.qdbp.able.jdbc.model.DbRawValue;
import com.gitee.qdbp.jdbc.model.DbType;
import com.gitee.qdbp.jdbc.model.DbVersion;
import com.gitee.qdbp.jdbc.model.OmitStrategy;
import com.gitee.qdbp.jdbc.plugins.DbPluginHelper;
import com.gitee.qdbp.jdbc.plugins.SqlDialect;
import com.gitee.qdbp.tools.utils.ConvertTools;
import com.gitee.qdbp.tools.utils.IndentTools;
import com.gitee.qdbp.tools.utils.StringTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * SQL容器
 *
 * @author zhaohuihua
 * @version 190601
 */
public class SqlBuffer implements Serializable {

    /** SerialVersionUID **/
    private static final long serialVersionUID = 1L;

    /** 数据库方言 **/
    private final SqlDialect dialect;
    /** 插件容器 **/
    private final DbPluginHelper plugins;

    /** 占位符当前序号 **/
    private int index;
    /** SQL缓存容器 **/
    private final List<Item> buffer;
    /** 快捷方式实例 **/
    private SqlBuilder shortcut;

    /** 构造函数 **/
    public SqlBuffer(DbType dbType, DbPluginHelper plugins) {
        this(plugins.buildSqlDialect(dbType), plugins);
    }

    /** 构造函数 **/
    public SqlBuffer(DbVersion version, DbPluginHelper plugins) {
        this(plugins.buildSqlDialect(version), plugins);
    }

    /** 构造函数 **/
    public SqlBuffer(SqlDialect dialect, DbPluginHelper plugins) {
        this.dialect = dialect;
        this.plugins = plugins;
        this.index = 0;
        this.buffer = new ArrayList<>();
    }

    /** 构造函数 **/
    protected SqlBuffer(SqlDialect dialect, DbPluginHelper plugins, SqlBuilder shortcut) {
        this(dialect, plugins);
        this.shortcut = shortcut;
    }

    /** 返回当前实例的快捷方式实例 **/
    public SqlBuilder shortcut() {
        if (this.shortcut == null) {
            this.shortcut = new SqlBuilder(this);
        }
        return this.shortcut;
    }

    /** 数据库方言 **/
    public SqlDialect sqlDialect() {
        return dialect;
    }

    /** 插件容器 **/
    public DbPluginHelper plugins() {
        return plugins;
    }

    /** 清除内容 **/
    public void clear() {
        this.buffer.clear();
    }

    /**
     * 将指定SQL片段追加到SQL后面
     * 
     * @param part SQL片段
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer append(String part) {
        StringItem si = getLastStringItem();
        si.append(part);
        return this;
    }

    /**
     * 将指定SQL片段追加到SQL后面
     * 
     * @param part SQL片段
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer append(char... part) {
        StringItem si = getLastStringItem();
        si.append(part);
        return this;
    }

    /**
     * 将指定SQL片段追加到SQL后面
     * 
     * @param part SQL片段
     * @param suffix 后缀
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer append(String part, char suffix) {
        StringItem si = getLastStringItem();
        si.append(part, suffix);
        return this;
    }

    /**
     * 将指定SQL片段追加到SQL后面
     * 
     * @param prefix 前缀
     * @param part SQL片段
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer append(char prefix, String part) {
        StringItem si = getLastStringItem();
        si.append(prefix, part);
        return this;
    }

    /**
     * 将指定SQL片段追加到SQL后面
     * 
     * @param prefix 前缀
     * @param part SQL片段
     * @param suffix 后缀
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer append(char prefix, String part, char suffix) {
        StringItem si = getLastStringItem();
        si.append(prefix, part, suffix);
        return this;
    }

    /**
     * 将指定SQL片段增加到SQL语句最前面
     * 
     * @param part SQL片段
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer prepend(String part) {
        StringItem si = getFirstStringItem();
        si.prepend(part);
        return this;
    }

    /**
     * 将指定SQL片段增加到SQL语句最前面
     * 
     * @param part SQL片段
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer prepend(char... part) {
        StringItem si = getFirstStringItem();
        si.prepend(part);
        return this;
    }

    /**
     * 将指定SQL片段增加到SQL语句最前面
     * 
     * @param part SQL片段
     * @param suffix 后缀
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer prepend(String part, char suffix) {
        StringItem si = getFirstStringItem();
        si.prepend(part, suffix);
        return this;
    }

    /**
     * 将指定SQL片段增加到SQL语句最前面
     * 
     * @param prefix 前缀
     * @param part SQL片段
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer prepend(char prefix, String part) {
        StringItem si = getFirstStringItem();
        si.prepend(prefix, part);
        return this;
    }

    /**
     * 将指定SQL片段增加到SQL语句最前面
     * 
     * @param prefix 前缀
     * @param part SQL片段
     * @param suffix 后缀
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer prepend(char prefix, String part, char suffix) {
        StringItem si = getFirstStringItem();
        si.prepend(prefix, part, suffix);
        return this;
    }

    /**
     * 将指定SQL片段追加到SQL语句后面
     * 
     * @param another SQL片段
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer append(SqlBuffer another) {
        if (another != null && !another.isEmpty()) {
            another.copyTo(this);
        }
        return this;
    }

    /**
     * 将指定SQL片段追加到SQL语句后面
     * 
     * @param prefix 前缀
     * @param another SQL片段
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer append(char prefix, SqlBuffer another) {
        if (another != null && !another.isEmpty()) {
            this.append(prefix);
            another.copyTo(this);
        }
        return this;
    }

    /**
     * 将指定SQL片段增加到SQL语句最前面
     * 
     * @param another SQL片段
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer prepend(SqlBuffer another) {
        if (another != null && !another.isEmpty()) {
            // 复制到临时缓冲区
            SqlBuffer temp = another.copy();
            // 序号递增, 空出前面的位置
            this.raiseIndex(another.index);
            // 增加到SQL语句最前面
            this.buffer.addAll(0, temp.buffer);
        }
        return this;
    }

    /**
     * 将指定SQL片段增加到SQL语句最前面
     * 
     * @param another SQL片段
     * @param suffix 后缀
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer prepend(SqlBuffer another, char suffix) {
        if (another != null && !another.isEmpty()) {
            this.prepend(suffix);
            // 序号递增, 空出前面的位置
            this.raiseIndex(another.index);
            // 增加到SQL语句最前面
            this.buffer.addAll(0, another.buffer);
        }
        return this;
    }

    private StringItem getLastStringItem() {
        Item last = this.buffer.isEmpty() ? null : this.buffer.get(this.buffer.size() - 1);
        if (last instanceof StringItem) {
            return (StringItem) last;
        } else {
            StringItem si = new StringItem();
            this.buffer.add(si);
            return si;
        }
    }

    private StringItem getFirstStringItem() {
        Item first = this.buffer.isEmpty() ? null : this.buffer.get(0);
        if (first instanceof StringItem) {
            return (StringItem) first;
        } else {
            StringItem si = new StringItem();
            this.buffer.add(0, si);
            return si;
        }
    }

    /** 序号递增, 空出前面的位置 **/
    protected void raiseIndex(int offset) {
        if (offset == 0) {
            return;
        }
        this.index += offset;
        for (Item item : this.buffer) {
            if (item instanceof VariableItem) {
                VariableItem variable = ((VariableItem) item);
                variable.index += offset;
            }
        }
    }

    /**
     * 增加变量, 同时将变量以占位符追加到SQL语句中
     * 
     * @param value 变量
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer addVariable(Object value) {
        if (value == null) {
            this.buffer.add(new VariableItem(index++, null));
        } else if (value instanceof SqlBuffer) {
            append((SqlBuffer) value);
        } else if (value instanceof SqlBuilder) {
            append(((SqlBuilder) value).out());
        } else if (value instanceof DbRawValue) {
            DbRawValue raw = (DbRawValue) value;
            this.buffer.add(new RawValueItem(raw.toString()));
        } else if (value instanceof Collection) {
            this.addVariables(new ArrayList<>((Collection<?>) value));
        } else if (value.getClass().isArray()) {
            this.addVariables(ConvertTools.parseList(value));
        } else if (value instanceof DbFieldName) {
            // this.append(value.toString());
            // 缺少环境数据, 无法将字段名转换为列名
            throw new IllegalArgumentException("CanNotSupportedVariableType: DbFieldName");
        } else {
            this.buffer.add(new VariableItem(index++, value));
        }
        return this;
    }

    private void addVariables(List<?> values) {
        List<?> items = SqlTools.duplicateRemoval(values);
        OmitStrategy omits = plugins.getOmitStrategyOfInSql();
        for (int i = 0, count = items.size(); i < count; i++) {
            Object value = items.get(i);
            if (i > 0) {
                this.append(',');
            }
            if (omits.getMinSize() > 0 && count > omits.getMinSize()) {
                this.tryOmit(i, count, omits.getKeepSize());
            }
            this.buffer.add(new VariableItem(index++, value));
        }
    }

    /** 删除左右两则的空白字符 **/
    public SqlBuffer trim() {
        trimLeft();
        trimRight();
        return this;
    }

    /** 删除左侧空白 **/
    public SqlBuffer trimLeft() {
        if (buffer.isEmpty()) {
            return this;
        }
        for (int i = 0, last = buffer.size() - 1; i <= last; i++) {
            Item item = buffer.get(i);
            if (item instanceof StringItem) {
                StringItem stringItem = (StringItem) item;
                int size = stringItem.trimLeft();
                if (size == 0) {
                    continue;
                }
            }
            return this;
        }
        return this;
    }

    /** 删除右侧空白 **/
    public SqlBuffer trimRight() {
        if (buffer.isEmpty()) {
            return this;
        }
        for (int i = buffer.size() - 1; i >= 0; i--) {
            Item item = buffer.get(i);
            if (item instanceof StringItem) {
                StringItem stringItem = (StringItem) item;
                int size = stringItem.trimRight();
                if (size == 0) {
                    continue;
                }
            }
            return this;
        }
        return this;
    }

    /**
     * 在第1个非空字符前插入前缀<br>
     * 删除prefixOverrides, 插入prefix<br>
     * 如果内容全部为空, 将不会做任何处理, 返回false<br>
     * 如果prefix为空, 又没有找到prefixOverrides, 也会返回false
     * 
     * @param prefix 待插入的前缀
     * @param prefixOverrides 待替换的前缀, 不区分大小写, 支持以|拆分的多个前缀, 如AND|OR
     * @return 是否有变更
     */
    public boolean insertPrefix(String prefix, String prefixOverrides) {
        if (buffer.isEmpty() || VerifyTools.isAllBlank(prefix, prefixOverrides)) {
            return false;
        }
        for (int i = 0; i < buffer.size(); i++) {
            Item item = buffer.get(i);
            if (item instanceof StringItem) {
                StringItem stringItem = (StringItem) item;
                boolean changed = stringItem.insertPrefix(prefix, prefixOverrides);
                if (changed) {
                    return true;
                }
            } else if (item instanceof VariableItem || item instanceof RawValueItem) {
                if (VerifyTools.isBlank(prefix)) {
                    return false;
                }
                StringItem element = new StringItem();
                element.append(prefix);
                // 如果前缀的结尾不是符号, 就要加空格; 符号排除了右括号, 因为右括号后面也要加空格
                if (!SqlTools.Text.endsWithSqlSymbol(prefix, ')')) {
                    element.append(' ');
                }
                buffer.add(i, element);
                return true;
            } else if (item instanceof OmitItem) {
                continue;
            } else {
                throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
            }
        }
        return false;
    }

    /**
     * 在最后1个非空字符后插入后缀<br>
     * 删除suffixOverrides, 插入suffix<br>
     * 如果内容全部为空, 将不会做任何处理, 返回false<br>
     * 如果suffix为空, 又没有找到suffixOverrides, 也会返回false
     * 
     * @param suffix 待插入的后缀
     * @param suffixOverrides 待替换的后缀, 不区分大小写, 支持以|拆分的多个后缀, 如AND|OR
     * @return 是否有变更
     */
    public boolean insertSuffix(String suffix, String suffixOverrides) {
        if (buffer.isEmpty() || VerifyTools.isAllBlank(suffix, suffixOverrides)) {
            return false;
        }
        for (int i = buffer.size() - 1; i >= 0; i--) {
            Item item = buffer.get(i);
            if (item instanceof StringItem) {
                StringItem stringItem = (StringItem) item;
                boolean changed = stringItem.insertSuffix(suffix, suffixOverrides);
                if (changed) {
                    return true;
                }
            } else if (item instanceof VariableItem || item instanceof RawValueItem) {
                if (VerifyTools.isBlank(suffix)) {
                    return false;
                }
                StringItem element = new StringItem();
                if (!SqlTools.Text.startsWithSqlSymbol(suffix)) {
                    element.append(' ');
                }
                element.append(suffix);
                buffer.add(i + 1, element);
                return true;
            } else if (item instanceof OmitItem) {
                continue;
            } else {
                throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
            }
        }
        return false;
    }

    public boolean isEmpty() {
        return buffer.isEmpty();
    }

    public boolean isBlank() {
        if (buffer.isEmpty()) {
            return true;
        }
        for (Item item : buffer) {
            if (item instanceof StringItem) {
                StringItem stringItem = (StringItem) item;
                String stringValue = stringItem.getValue().toString();
                if (stringValue.trim().length() > 0) {
                    return false;
                }
            } else if (item instanceof RawValueItem) {
                RawValueItem rawValueItem = (RawValueItem) item;
                String stringValue = rawValueItem.getValue();
                if (stringValue.trim().length() > 0) {
                    return false;
                }
            } else if (item instanceof VariableItem) {
                VariableItem variableItem = (VariableItem) item;
                if (variableItem.getValue() != null) {
                    return false;
                }
            } else if (item instanceof OmitItem) {
                continue;
            } else {
                throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
            }
        }
        return true;
    }

    /**
     * 超级长的SQL在输出日志时可以省略掉一部分<br>
     * 省略哪一部分, 用startOmit()/endOmit()来标识起止位置
     * 
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer startOmit() {
        this.buffer.add(new OmitItem(true));
        return this;
    }

    /**
     * 超级长的SQL在输出日志时可以省略掉一部分<br>
     * 省略哪一部分, 用startOmit()/endOmit()来标识起止位置
     * 
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer endOmit() {
        this.buffer.add(new OmitItem(false));
        return this;
    }

    /**
     * 传入size和当前index, 自动计算, 插入省略标记<br>
     * 日志只取前3行+后3行; 因此在第3行后面开始省略, 在后3行之前结束省略<br>
     * 注意: 如果有换行符, 最好放在换行符后面
     * 
     * @param index 当前行数
     * @param count 总行数
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer tryOmit(int index, int count) {
        return tryOmit(index, count, 3);
    }

    /**
     * 传入size和当前index, 自动计算, 插入省略标记<br>
     * 日志只取前3行+后3行; 因此在第3行后面开始省略, 在后3行之前结束省略<br>
     * 注意: 如果有换行符, 最好放在换行符后面
     * 
     * @param index 当前行数(从0开始)
     * @param count 总行数
     * @param limit 保留多少行(前后各保留limit行)
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer tryOmit(int index, int count, int limit) {
        if (count <= limit * 2 + 2) {
            // 如果总行数只比保留行数多2行, 就没必要省略了
            return this;
        }
        if (index == limit) {
            this.buffer.add(new OmitItem(true, index)); // 开始省略
        } else if (index == count - limit) {
            this.buffer.add(new OmitItem(false, index)); // 结束省略
        }
        return this;
    }

    /**
     * 所有缩进增加或减少n个TAB
     * 
     * @param size 缩进量: 正数为增加, 负数为减少
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer tabAll(int size) {
        return this.tabAll(size, true);
    }

    /**
     * 所有缩进增加或减少n个TAB
     * 
     * @param size 缩进量: 正数为增加, 负数为减少
     * @param leading 开头要不要缩进
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer tabAll(int size, boolean leading) {
        if (size == 0) {
            return this;
        }

        boolean first = true;
        boolean needPrepend = false; // 以VariableItem开头, 且leading=true且size>0时, 需要在前面添加TAB
        for (Item item : this.buffer) {
            if (item == null) {
                continue;
            }
            if (item instanceof StringItem) {
                StringItem temp = (StringItem) item;
                if (temp.value.length() == 0) {
                    continue;
                }
                // blankLineIndent=空行要不要缩进, 一定要传true
                // 因为这里的value只是片段, 后面可能还有内容, 并不能判断空行
                IndentTools.space.tabAll(temp.value, size, first && leading, true);
                first = false;
            } else if (item instanceof RawValueItem) {
                RawValueItem temp = (RawValueItem) item;
                if (temp.value.length() == 0) {
                    continue;
                }
                temp.setValue(IndentTools.space.tabAll(temp.value, size, first && leading, true));
                first = false;
            } else if (item instanceof VariableItem) {
                if (first) {
                    first = false;
                    if (leading && size > 0) {
                        needPrepend = true;
                    }
                }
            } else if (item instanceof OmitItem) {
                continue;
            } else {
                throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
            }
        }

        if (needPrepend) {
            this.prepend(IndentTools.getIndenTabs(size));
        }

        return this;
    }

    /**
     * 所有缩进设置为1个TAB, 以最小的缩进为基准
     * 
     * @return 返回当前SQL容器用于连写
     * @deprecated 含义不明晰<br>
     *             如果是设置为1个TAB, 应改为indentAll(1);<br>
     *             如果是在原有的基础上缩进1个TAB, 应改为tabAll(1);
     */
    @Deprecated
    public SqlBuffer indentAll() {
        return indentAll(1, true);
    }

    /**
     * 所有缩进设置为n个TAB, 以最小的缩进为基准
     * 
     * @param size 缩进量: 必须为正数或0, 负数视为0
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer indentAll(int size) {
        return this.indentAll(size, true);
    }

    /**
     * 所有缩进设置为n个TAB, 以最小的缩进为基准
     * 
     * @param size 缩进量: 必须为正数或0, 负数视为0
     * @param leading 开头要不要缩进
     * @return 返回当前SQL容器用于连写
     */
    public SqlBuffer indentAll(int size, boolean leading) {
        // minIndent<0表示未找到缩进, 只有leading=false才有可能<0
        int minIndent = SqlTools.Indent.countMinIndentSize(this, leading);
        if (minIndent < 0 || size == minIndent) {
            return this;
        }
        return this.tabAll(size - minIndent, leading);
    }

    /**
     * 复制
     * 
     * @return 副本
     */
    public SqlBuffer copy() {
        SqlBuffer target = new SqlBuffer(dialect, plugins);
        this.copyTo(target);
        return target;
    }

    /**
     * 复制到另一个缓存容器中
     * 
     * @param target 目标容器
     */
    public void copyTo(SqlBuffer target) {
        for (Item item : this.buffer) {
            if (item instanceof StringItem) {
                StringItem stringItem = (StringItem) item;
                target.getLastStringItem().append(stringItem.getValue().toString());
            } else if (item instanceof VariableItem) {
                VariableItem variable = ((VariableItem) item);
                target.buffer.add(new VariableItem(target.index++, variable.getValue()));
            } else if (item instanceof RawValueItem) {
                RawValueItem rawItem = (RawValueItem) item;
                target.buffer.add(new RawValueItem(rawItem.getValue()));
            } else if (item instanceof OmitItem) {
                OmitItem omitItem = (OmitItem) item;
                target.buffer.add(new OmitItem(omitItem.enabled(), omitItem.index()));
            } else {
                throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
            }
        }

    }

    /** 获取预编译SQL语句 **/
    public String getPreparedSqlString() {
        StringBuilder sql = new StringBuilder();
        for (Item item : this.buffer) {
            if (item instanceof StringItem) {
                StringItem stringItem = (StringItem) item;
                sql.append(stringItem.getValue());
            } else if (item instanceof VariableItem) {
                sql.append(':').append(((VariableItem) item).getKey());
            } else if (item instanceof RawValueItem) {
                RawValueItem rawValueItem = (RawValueItem) item;
                sql.append(plugins.resolveRawValue(rawValueItem.getValue(), dialect));
            } else if (item instanceof OmitItem) {
                continue;
            } else {
                throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
            }
        }
        return sqlFormatToString(sql);
    }

    /** 获取预编译SQL参数 **/
    public Map<String, Object> getPreparedVariables() {
        Map<String, Object> map = new HashMap<>();
        for (Item item : this.buffer) {
            if (item instanceof VariableItem) {
                VariableItem variable = ((VariableItem) item);
                map.put(variable.getKey(), plugins.variableToDbValue(variable.getValue(), dialect));
            }
        }
        return map;
    }

    /** 获取可执行SQL语句(预编译参数替换为拼写式参数) **/
    public String getExecutableSqlString() {
        StringBuilder sql = new StringBuilder();
        for (Item item : this.buffer) {
            if (item instanceof StringItem) {
                StringItem stringItem = (StringItem) item;
                sql.append(stringItem.getValue());
            } else if (item instanceof VariableItem) {
                VariableItem variable = ((VariableItem) item);
                String string = plugins.variableToString(variable.value, dialect);
                sql.append(string);
            } else if (item instanceof RawValueItem) {
                RawValueItem rawValueItem = (RawValueItem) item;
                sql.append(plugins.resolveRawValue(rawValueItem.getValue(), dialect));
            } else if (item instanceof OmitItem) {
                continue;
            } else {
                throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
            }
        }
        return sqlFormatToString(sql);
    }

    /**
     * 获取用于日志输出的SQL语句(预编译参数替换为拼写式参数)<br>
     * 如果参数值长度超过100会被截断(例如大片的HTML富文本代码等)<br>
     * 批量SQL会省略部分语句不输出到日志中(几百几千个批量操作会导致SQL太长)
     * 
     * @return SQL语句
     */
    public String getLoggingSqlString() {
        return getLoggingSqlString(true);
    }

    /**
     * 获取用于日志输出的SQL语句(预编译参数替换为拼写式参数)
     * 
     * @param omitMode 是否使用省略模式<br>
     *            如果参数值长度超过100会被截断(例如大片的HTML富文本代码等);<br>
     *            批量SQL会省略部分语句不输出到日志中(几百几千个批量操作会导致SQL太长)
     * @return SQL语句
     */
    public String getLoggingSqlString(boolean omitMode) {
        return getLoggingSqlString(omitMode, true);
    }

    private String getLoggingSqlString(boolean omitMode, boolean format) {
        int valueLimit = 100;
        StringBuilder sql = new StringBuilder();
        int charCount = 0;
        int lineCount = 0;
        // 解决OmitItem嵌套的问题
        Stack<OmitItem> omitStacks = new Stack<>();
        for (Item item : this.buffer) {
            if (item instanceof StringItem) {
                StringItem stringItem = (StringItem) item;
                String stringValue = stringItem.getValue().toString();
                if (omitMode && !omitStacks.isEmpty()) { // 省略模式下, 只需要统计数量
                    charCount += stringValue.length();
                    lineCount += IndentTools.countNewlineChars(stringValue);
                } else {
                    sql.append(stringValue);
                }
            } else if (item instanceof VariableItem) {
                VariableItem variable = ((VariableItem) item);
                String stringValue = plugins.variableToString(variable.value, dialect);
                if (omitMode && !omitStacks.isEmpty()) { // 省略模式下, 只需要统计数量
                    charCount += stringValue == null ? 4 : stringValue.length();
                    lineCount += stringValue == null ? 0 : IndentTools.countNewlineChars(stringValue);
                } else {
                    if (omitMode) { // 省略模式下, 截短超过长度的字段值
                        stringValue = tryCutStringOverlength(stringValue, valueLimit);
                    }
                    sql.append(stringValue);
                    sql.append("/*").append(variable.getKey()).append("*/");
                }
            } else if (item instanceof RawValueItem) {
                RawValueItem rawValueItem = (RawValueItem) item;
                String stringValue = plugins.resolveRawValue(rawValueItem.getValue(), dialect);
                if (omitMode && !omitStacks.isEmpty()) { // 省略模式下, 只需要统计数量
                    charCount += stringValue == null ? 4 : stringValue.length();
                    lineCount += stringValue == null ? 0 : IndentTools.countNewlineChars(stringValue);
                } else {
                    sql.append(stringValue);
                }
            } else if (item instanceof OmitItem) {
                if (!omitMode) {
                    continue;
                }
                boolean omitEnabled = !omitStacks.isEmpty();
                OmitItem omitItem = (OmitItem) item;
                OmitItem srartItem = null;
                // 开始标记入栈, 结束标记出栈
                if (omitItem.enabled()) {
                    omitStacks.add(omitItem);
                } else {
                    if (!omitStacks.isEmpty()) {
                        srartItem = omitStacks.pop();
                    // } else {
                        // 结束标记多于开始标记, 不作处理
                    }
                }
                // OmitItem堆栈之前不是空的, 现在变空了, 说明已经回到顶层且结束了
                if (omitEnabled && omitStacks.isEmpty()) {
                    if (charCount > 0) { // 插入省略信息
                        int itemCount = calcOmittedIndex(srartItem, omitItem);
                        insertOmittedDetails(sql, charCount, lineCount, itemCount);
                    }
                    // 统计信息归零
                    lineCount = 0;
                    charCount = 0;
                }
            } else {
                throw new UnsupportedOperationException("Unsupported item: " + item.getClass());
            }
        }
        // 到最后了, OmitItem还不是空的, 说明开始标记多于结束标记
        if (!omitStacks.isEmpty() && charCount > 0) { // 插入省略信息
            insertOmittedDetails(sql, charCount, lineCount, -1);
        }
        return format ? sqlFormatToString(sql) : sql.toString();
    }

    /** 替换\t为4个空格, 替换\r\n为\n, 替换单独的\r为\n; 清除末尾的空白 **/
    protected String sqlFormatToString(StringBuilder sql) {
        StringTools.replace(sql, "\t", "    ", "\r\n", "\n", "\r", "\n");
        // 清除末尾的空白
        int size = sql.length();
        int lastIndex = size;
        // 从最后开始, 判断前一个字符是不是空白
        for (int i = size; i > 0; i--) {
            char c = sql.charAt(i - 1); // 判断前一个字符
            if (!StringTools.isAsciiWhitespace(c)) {
                lastIndex = i;
                break;
            }
        }
        if (lastIndex < size) {
            sql.setLength(lastIndex);
        }
        return sql.toString();
    }

    protected void insertOmittedDetails(StringBuilder sql, int charCount, int lineCount, int itemCount) {
        // 计算省略掉的行数和字符数信息
        // /* 10000 chars, 100 lines, 50 items are omitted here ... */
        StringBuilder msg = generateOmittedDetails(charCount, lineCount, itemCount);
        // 在最后一个换行符之后插入省略信息
        IndentTools.space.insertMessageAfterLastNewline(sql, msg);
    }

    // 生成省略掉的行数和字符数详细描述
    // /* 10000 chars, 100 lines, 50 items are omitted here ... */
    protected StringBuilder generateOmittedDetails(int charCount, int lineCount, int itemCount) {
        StringBuilder msg = new StringBuilder();
        msg.append("/*").append(' ');
        msg.append(charCount).append(' ').append("chars");
        if (lineCount > 0) {
            msg.append(',').append(' ').append(lineCount).append(' ').append("lines");
        }
        if (itemCount > 0) {
            msg.append(',').append(' ').append(itemCount).append(' ').append("items");
        }
        msg.append(' ').append("are omitted here").append(' ').append("...").append(' ').append("*/");
        return msg;
    }

    private int calcOmittedIndex(OmitItem start, OmitItem end) {
        if (start == null || end == null) {
            return -1;
        }
        if (start.index() < 0 || end.index() < 0 || start.index() > end.index()) {
            return -1;
        }
        return end.index() - start.index();
    }

    /** 尝试截短字符串 **/
    protected static String tryCutStringOverlength(String string, int limit) {
        if (limit <= 0) {
            return string;
        }
        if (!string.startsWith("'") || !string.endsWith("'")) {
            return string;
        }
        int length = string.length() - 2;
        if (length <= limit) {
            return string;
        }
        string = string.substring(1, string.length() - 1);
        string = StringTools.ellipsis(string, limit) + '(' + length + ')';
        return "'" + string + "'";
    }

    public String toString() {
        return getLoggingSqlString(true, false);
    }

    protected List<Item> items() {
        return this.buffer;
    }

    /** 自动追加空格 **/
    protected SqlBuffer autoAppendWhitespace(String part) {
        VerifyTools.requireNonNull(part, "part");
        // 1. 空白后/符号后(右括号除外)不加
        // 2. 空白前/符号前不加
        // 3. 两个单词放一块时要加
        // -- '作为单词而不是符号, SELECT '0' AS price

        if (this.isEmpty() || part.isEmpty()) {
            return this;
        }
        if (SqlTools.Text.startsWithChar(part, '(') && IndentTools.countNewlineChars(part) > 0) {
            this.append(' ');
            return this;
        }
        // 左侧是除右括号以外的空白或符号, 或者右侧是空白或符号, 不需要加空格
        if (SqlTools.Text.endsWithSqlSymbol(this, ')') || SqlTools.Text.startsWithSqlSymbol(part)) {
            return this;
        }
        this.append(' ');

        // TODO
        // 运算符, 如果前面有空格, 则后面也加一个空格
        // 换行时, 前面的未闭合的左括号前加一个空格
        // 右括号前, 如果在本行找不到成对的左括号时, 加一个空格
        // 右括号后, 如果右括号前有空格, 后面也加一个空格
        return this;
    }

    /** 自动追加空格 **/
    protected SqlBuffer autoAppendWhitespace(SqlBuffer part) {
        VerifyTools.requireNonNull(part, "part");
        if (this.isEmpty() || part.isEmpty()) {
            return this;
        }
        if (SqlTools.Text.startsWithChar(part, '(') && SqlTools.Text.countNewlineChars(part) > 0) {
            this.append(' ');
            return this;
        }
        // 左侧是除右括号以外的空白或符号, 或者右侧是空白或符号, 不需要加空格
        if (SqlTools.Text.endsWithSqlSymbol(this, ')') || SqlTools.Text.startsWithSqlSymbol(part)) {
            return this;
        }
        this.append(' ');
        return this;
    }

    /** 自动追加空格 **/
    protected SqlBuffer autoAppendWhitespace() {
        if (this.isEmpty()) {
            return this;
        }
        if (SqlTools.Text.endsWithSqlSymbol(this, ')')) {
            return this;
        }
        this.append(' ');

        return this;
    }

    /** 自动在前面添加空格 **/
    protected SqlBuffer autoPrependWhitespace(String part) {
        VerifyTools.requireNonNull(part, "part");
        if (this.isEmpty() || part.isEmpty()) {
            return this;
        }
        if (SqlTools.Text.startsWithChar(this, '(') && SqlTools.Text.countNewlineChars(this) > 0) {
            this.prepend(' ');
            return this;
        }
        // 左侧是除右括号以外的空白或符号, 或者右侧是空白或符号, 不需要加空格
        if (SqlTools.Text.endsWithSqlSymbol(part, ')') || SqlTools.Text.startsWithSqlSymbol(this)) {
            return this;
        }
        this.prepend(' ');
        return this;
    }

    /** 自动在前面添加空格 **/
    protected SqlBuffer autoPrependWhitespace(SqlBuffer part) {
        VerifyTools.requireNonNull(part, "part");
        if (this.isEmpty() || part.isEmpty()) {
            return this;
        }
        if (SqlTools.Text.startsWithChar(this, '(') && SqlTools.Text.countNewlineChars(this) > 0) {
            this.prepend(' ');
            return this;
        }
        // 左侧是除右括号以外的空白或符号, 或者右侧是空白或符号, 不需要加空格
        if (SqlTools.Text.endsWithSqlSymbol(part, ')') || SqlTools.Text.startsWithSqlSymbol(this)) {
            return this;
        }
        this.prepend(' ');
        return this;
    }

    /** 清除最前面的缩进空白(返回清除了几个缩进量) **/
    protected int clearLeadingIndentWhitespace() {
        return SqlTools.Indent.clearLeadingIndentWhitespace(this);
    }

    /** 清除最后的文字后面的缩进空白(返回清除了几个缩进量) **/
    protected int clearTrailingIndentWhitespace() {
        return SqlTools.Indent.clearTrailingIndentWhitespace(this);
    }

    protected interface Item {
    }

    /**
     * 超级长的SQL在输出日志时可以省略掉一部分<br>
     * 省略哪一部分, 用OmitItem来标识起止位置
     *
     * @author zhaohuihua
     * @version 20200712
     */
    protected static class OmitItem implements Item, Serializable {

        /** SerialVersionUID **/
        private static final long serialVersionUID = 1L;
        private final boolean enabled;
        private final int index;

        public OmitItem(boolean enabled) {
            this.enabled = enabled;
            this.index = -1;
        }

        public OmitItem(boolean enabled, int index) {
            this.enabled = enabled;
            this.index = index;
        }

        public boolean enabled() {
            return enabled;
        }

        public int index() {
            return index;
        }
    }

    protected static class RawValueItem implements Item, Serializable {

        /** SerialVersionUID **/
        private static final long serialVersionUID = 1L;
        private String value;

        public RawValueItem(String value) {
            VerifyTools.requireNonNull(value, "value");
            this.value = value;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }

    protected static class StringItem implements Item, Serializable {

        /** SerialVersionUID **/
        private static final long serialVersionUID = 1L;

        private final StringBuilder value;

        public StringItem() {
            this.value = new StringBuilder();
        }

        public void append(char... chars) {
            this.value.append(chars);
        }

        public void append(String value) {
            VerifyTools.requireNonNull(value, "value");
            this.value.append(value);
        }

        public void append(String value, char suffix) {
            VerifyTools.requireNonNull(value, "value");
            this.value.append(value).append(suffix);
        }

        public void append(char prefix, String value) {
            VerifyTools.requireNonNull(value, "value");
            this.value.append(prefix).append(value);
        }

        public void append(char prefix, String value, char suffix) {
            VerifyTools.requireNonNull(value, "value");
            this.value.append(prefix).append(value).append(suffix);
        }

        public void prepend(char... chars) {
            this.value.insert(0, chars);
        }

        public void prepend(String value) {
            VerifyTools.requireNonNull(value, "value");
            this.value.insert(0, value);
        }

        public void prepend(String value, char suffix) {
            VerifyTools.requireNonNull(value, "value");
            this.value.insert(0, suffix).insert(0, value);
        }

        public void prepend(char prefix, String value) {
            VerifyTools.requireNonNull(value, "value");
            this.value.insert(0, value).insert(0, prefix);
        }

        public void prepend(char prefix, String value, char suffix) {
            VerifyTools.requireNonNull(value, "value");
            this.value.insert(0, suffix).insert(0, value).insert(0, prefix);
        }

        /**
         * 删除左侧空白
         * 
         * @return 删除后剩下多少字符
         */
        public int trimLeft() {
            return trimLeftRight(true, false);
        }

        /**
         * 删除右侧空白
         * 
         * @return 删除后剩下多少字符
         */
        public int trimRight() {
            return trimLeftRight(false, true);
        }

        private int trimLeftRight(boolean trimLeft, boolean trimRight) {
            if (value.length() == 0) {
                return 0;
            }

            int size = value.length();
            char[] chars = value.toString().toCharArray();

            if (trimLeft) {
                int start = 0;
                while ((start < size) && (chars[start] <= ' ')) {
                    start++;
                }
                if (start > 0) {
                    value.delete(0, start);
                }
            }
            if (trimRight) {
                int end = size;
                while ((end > 0) && (chars[end - 1] <= ' ')) {
                    end--;
                }
                if (end < size) {
                    value.delete(end, size);
                }
            }
            return value.length();
        }

        /**
         * 在第1个非空字符前插入前缀<br>
         * 删除prefixOverrides, 插入prefix<br>
         * 如果内容全部为空, 将不会做任何处理, 返回false<br>
         * 如果prefix为空, 又没有找到prefixOverrides, 也会返回false
         * 
         * @param prefix 待插入的前缀
         * @param prefixOverrides 待替换的前缀, 不区分大小写, 支持以|拆分的多个前缀, 如AND|OR
         * @return 是否有变更
         */
        public boolean insertPrefix(String prefix, String prefixOverrides) {
            if (value.length() == 0) {
                return false;
            }

            int last = value.length() - 1;
            int position = -1;
            // 查找插入点: 第1个非空字符的位置
            for (int i = 0; i <= last; i++) {
                char c = value.charAt(i);
                if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
                    continue;
                } else {
                    position = i;
                    break; // 位置找到了
                }
            }
            if (position < 0) {
                return false;
            }
            boolean removed = false;
            boolean changed = false;
            if (VerifyTools.isNotBlank(prefixOverrides)) {
                String[] array = StringTools.split(prefixOverrides, '|');
                for (String prefixOverride : array) {
                    if (startsWith(value, prefixOverride, position)) {
                        value.delete(position, position + prefixOverride.length());
                        removed = true;
                        changed = true;
                        break;
                    }
                }
            }
            if (VerifyTools.isNotBlank(prefix)) {
                changed = true;
                if (removed) {
                    value.insert(position, prefix);
                } else {
                    String suffix = value.substring(position, Math.min(position + 10, value.length()));
                    if (SqlTools.Text.endsWithSqlSymbol(prefix, ')') || SqlTools.Text.startsWithSqlSymbol(suffix)) {
                        value.insert(position, prefix);
                    } else {
                        value.insert(position, prefix + ' ');
                    }
                }
            }
            return changed;
        }

        /**
         * 在最后1个非空字符后插入后缀<br>
         * 删除suffixOverrides, 插入suffix<br>
         * 如果内容全部为空, 将不会做任何处理, 返回false<br>
         * 如果suffix为空, 又没有找到suffixOverrides, 也会返回false
         * 
         * @param suffix 待插入的后缀
         * @param suffixOverrides 待替换的后缀, 不区分大小写, 支持以|拆分的多个前缀, 如AND|OR
         * @return 是否有变更
         */
        public boolean insertSuffix(String suffix, String suffixOverrides) {
            if (value.length() == 0) {
                return false;
            }

            int last = value.length() - 1;
            int position = -1;
            // 查找插入点: 第1个非空字符之后的那个位置
            for (int i = last; i >= 0; i--) {
                char c = value.charAt(i);
                if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
                    continue;
                } else {
                    position = i + 1;
                    break; // 位置找到了
                }
            }
            // 停在第1个非空字符之后, 所以不能等于0
            if (position <= 0) {
                return false;
            }
            boolean removed = false;
            boolean changed = false;
            if (VerifyTools.isNotBlank(suffixOverrides)) {
                String[] array = StringTools.split(suffixOverrides, '|');
                for (String suffixOverride : array) {
                    if (endsWith(value, suffixOverride, position)) {
                        value.delete(position - suffixOverride.length(), position);
                        position -= suffixOverride.length();
                        removed = true;
                        changed = true;
                        break;
                    }
                }
            }
            if (VerifyTools.isNotBlank(suffix)) {
                changed = true;
                if (removed) {
                    value.insert(position, suffix);
                } else {
                    String prefix = value.substring(Math.max(position - 10, 0), position);
                    if (SqlTools.Text.endsWithSqlSymbol(prefix, ')') || SqlTools.Text.startsWithSqlSymbol(suffix)) {
                        value.insert(position, suffix);
                    } else {
                        value.insert(position, ' ' + suffix);
                    }
                }
            }
            return changed;
        }

        private static boolean startsWith(CharSequence string, String prefix, int index) {
            if (string.length() < index + prefix.length()) {
                return false;
            }
            int i = 0;
            for (; i < prefix.length(); i++) {
                if (Character.toUpperCase(prefix.charAt(i)) != Character.toUpperCase(string.charAt(i + index))) {
                    return false;
                }
            }
            // 如果prefix是单词结尾, 则下一个字符必须不是单词
            // 例如替换OR, 遇到ORG_ID, 应判定为不匹配
            if (i + index < string.length() && StringTools.isWordChar(prefix.charAt(prefix.length() - 1))) {
                return !StringTools.isWordChar(string.charAt(i + index));
            }
            return true;
        }

        private static boolean endsWith(CharSequence string, String suffix, int index) {
            int si = index - suffix.length();
            if (si < 0) {
                return false;
            }
            for (int i = 0; i < suffix.length(); i++) {
                if (Character.toUpperCase(suffix.charAt(i)) != Character.toUpperCase(string.charAt(si + i))) {
                    return false;
                }
            }
            // 如果suffix是单词开头, 则前一个字符必须不是单词
            if (si > 0 && StringTools.isWordChar(suffix.charAt(0))) {
                return !StringTools.isWordChar(string.charAt(si - 1));
            }
            return true;
        }

        public StringBuilder getValue() {
            return this.value;
        }

        public String toString() {
            return this.value.toString();
        }
    }

    protected static class VariableItem implements Item, Serializable {

        /** SerialVersionUID **/
        private static final long serialVersionUID = 1L;

        private int index;
        private final Object value;

        public VariableItem(int index, Object value) {
            this.index = index;
            this.value = value;
        }

        public String getKey() {
            return "$" + (index + 1);
        }

        public Object getValue() {
            return this.value;
        }

        public String toString() {
            return this.getKey();
        }
    }
}
